#ifndef __SINK__
#define __SINK__
#include <fstream>
#include <cstring>
#include "Format.hpp"
#include "LogMsg.hpp"

/*
    LogSink：日志落地方向
    1.将日志消息输出到哪？
        1.  标准输出
        2.  文件
        3.  滚动文件,根据文件大小进行滚动，即当文件大小为1MB时，重新创建一个文件继续写入
        4.  支持扩展，支持其他落地方向
    2.  使用简单工厂模式，每个产品对应一个工厂，即根据不同的落地方向可以创建出不同的类，是创建对象！
        并不仅仅定义类
*/

namespace Log
{

    class LogSink
    {
    public:
        // 这里使用继承和多态，为了简洁方便，重命名智能指针
        // 在继承和多态中，一定是使用基类的指针去指向派生类
        using ptr = std::shared_ptr<LogSink>;
        LogSink()
        {}

        // 不同落地方向存在不同的资源情况，也就对应的不同的析构
        virtual ~LogSink()
        {}
        
        // virtual void sink(const LogMsg& msg) = 0;
        // 这里为什么不用LogMsg作为参数？
        // 1.如果做参数结构冗余，不同落地方向sink中都需要，format,再获取str，再进行落地
        // 2.结构更复杂，我们可以让外层来format,我们只负责落地的data
        virtual void log(const char* data,const size_t size) = 0;
    };

    class StdoutLogSink : public LogSink
    {
    public:
        StdoutLogSink()
        {}
        ~StdoutLogSink()
        {}

        using ptr = std::shared_ptr<StdoutLogSink>;
        void log(const char* data,const size_t size)
        {
            // 并不能保证data是个字符串，以\0结尾
            // 所以我们不能cout << data !!
            // 只能通过write
            std::cout.write(data,size);
        }
    };

    class FileLogSink:public LogSink
    {
    private:
        std::string _filename;
        std::ofstream _ofs; 

    public:
        using ptr = std::shared_ptr<FileLogSink>;
        FileLogSink(std::string filename = "../LogFile/logfile.txt")
        :_filename(filename)
        {
            // 1.如果目录创建失败,终止程序
            if(Util::createDirectory(Util::path(_filename)) == false)
            {
                std::cout << "FileLogSink::createDirectory fail" << std::endl;
                return;
            }

            // 2.以追加方式 打开文件,如果文件不存在ofstream 会创建文件
            // 不能out方式，这是覆盖式写入
            _ofs.open(_filename, std::ios::binary | std::ios::app);
            if(_ofs.is_open() == false )
            {
                std::cout << "FileLogSink::is_open fail" << std::endl;
            }
        }
        // 关闭文件
        ~FileLogSink()
        {
            _ofs.close();
        }

        std::string fileName()
        {
            return _filename;
        }
        void log(const char* data,const size_t size)
        {
            _ofs.write(data,size);
            if(_ofs.good() == false)
            {
                std::cout << "FileLogSink::log fail" << std::endl; 
            }
        }
    };

    class RollBySizeLogSink:public LogSink
    {
    private:
        // ./LogFile/logfileRollBySize-
        // ./LogFile/logfileRollBySize-时间.txt
        std::string _file_prefix; // 文件名前缀
        std::ofstream _ofs; 
        std::size_t _filesz; // 文件总大小 
        std::size_t _cur_sz; // 当前文件大小
    private:
        std::string GetFileName()
        {
            time_t time = Util::now();
            struct tm t;
            localtime_r(&time,&t);
            // 可以使用strftime进行转换也可以自己转换
            std::stringstream ss;
            // ./LogFile/logfileRollBySize2024-2-25 18:13:30.txt
            ss << _file_prefix;
            ss << t.tm_year + 1900  << t.tm_mon + 1 <<  t.tm_wday + 1;
            ss << t.tm_hour <<  t.tm_min <<  t.tm_sec << ".txt";
            return ss.str();
        }

    public:
        using ptr = std::shared_ptr<RollBySizeLogSink>;
        RollBySizeLogSink(std::string file_prefix = "../LogFile/RBySz-",
                        size_t filesz = 1024):
                        _file_prefix(file_prefix),
                        _filesz(filesz),
                        _cur_sz(0)
        {
            // 1.创建目录
            if(Util::createDirectory(Util::path(_file_prefix)) == false)
            {
                std::cout << "RollBySizeLogSink::createDirectory fail" << std::endl;
                return;
            }
        }

        void InitFile()
        {
            // 关闭之前打开的文件
            // 处理文件不存在的情况 -- is_open
            if(_cur_sz > _filesz || _ofs.is_open() == false)
            {
                // std::cout << "_cur_sz：" << _cur_sz << std::endl;
                // 打开的文件才关闭
                // if(_ofs.is_open() == true)
                    _ofs.close();
                // debug
                // sleep(1);
                std::string name = GetFileName();
                // std::cout << name << std::endl;
                _ofs.open(name,std::ios::binary | std::ios::app);
                if(_ofs.is_open() == false)
                {
                    std::cout << "RollBySizeLogSink::is_open failed" << errno << strerror(errno) << " " << name << std::endl;
                    return;
                }
                _cur_sz = 0;
            }
        }

        // 关闭文件
        ~RollBySizeLogSink()
        {
            _ofs.close();
        }

        void log(const char* data,const size_t size)
        {
            InitFile();
            _ofs.write(data,size);
            if(_ofs.good() == false)
            {
                std::cout << "RollBySizeLogSink::log failed" << errno << strerror(errno) << std::endl;
                return;
            }
            _cur_sz += size;
        }
    };



    // 使用简单工厂模式去创建不同落地方向的类

    // 方案1
    // 如果像下面这么写也是可以的但是好繁琐
    // 有没有更简的方案？有的

    // class LogSinkFactory
    // {
    // public:
    //     virtual LogSink::ptr create() = 0;
    // };
    // class StdoutSinkFactory:public LogSinkFactory
    // {
    // public:
    //      LogSink::ptr create()
    //      {
    //         return  LogSink::ptr(new StdoutLogSink);
    //      }
    // };
    // class FileSinkFactory:public LogSinkFactory
    // {
    // public:
    //     LogSink::ptr create() 
    //     {
    //         return  LogSink::ptr(new FileLogSink);
    //     }
    // };
    // class RollFileSinkFactory:public LogSinkFactory
    // {
    // public:
    //     LogSink::ptr create()
    //     {
    //         return  LogSink::ptr(new RollBySizeLogSink);
    //     }
    // };

    class LogSinkFactory
    {
    public:
        // 方案2 也不推荐,开闭原则不好

        // LogSink::ptr create(int type)
        // {
        //     switch(type)
        //     {
        //         case 1:
        //             return  LogSink::ptr(new StdoutLogSink);
        //         // ...
        //     }
        //}

        // 方案3
        // 使用函数模板
        // 又因为不同的落地方向中构造函数的参数数量不一样
        // 所以我们还要可以使用可变参数的模板

        // 使用万能引用

        // 注意：
        // 在模板中，通常需要使用 typename 关键字来指示其后的符号是一个类型。
        // 在这里，typename LogSink::ptr 中的 typename 表示 ptr 是一个类型而不是成员变量。
        // 如果省略 typename，编译器可能会将 ptr 视为成员变量，导致错误

        // 工厂模式通用模板
        // 这里就能体现出，落地方式的拓展性
        // 拓展的落地方式只需要继承LogSink即可

        // SinkType这个是类型，Args这个是参数!
        // LogSinkFactory::create<MySinkType> (arg1, arg2, ...);  !!!
        template<class SinkType,class ...Args>
        static typename LogSink::ptr create(Args&& ...args)
        {
            //auto mySink = LogSinkFactory::create<MySinkType>(arg1, arg2, ...);
            return std::make_shared<SinkType>(std::forward<Args>(args)...);
        }

    };

}

#endif